package com.qipay.thirdpart.utils;

import com.google.gson.Gson;
import com.qipay.thirdpart.config.ResponseStateEnum;
import com.qipay.thirdpart.config.TFB8ResponseStateEnum;
import com.qipay.thirdpart.vo.PayResponse;
import com.qipay.thirdpart.vo.TFB8Response;
import com.qipay.utils.ParamUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.TreeMap;

public class RequestUtil {

    private static final Logger logger = LoggerFactory.getLogger(RequestUtil.class);
    private static final Gson gson = new Gson();

    // 签名
    private static String sign;

    // 服务端返回的数据
    private static String responseData;

    /**
     * 发起请求
     *
     * @param url
     * @param param 参数列表（按首字母进行排序）
     */
    public static void check(final String url, TreeMap<String, String> param, String pwd, String privateKeyPath, String publicKeyPath) {
        logger.info("拼接签名原串-----------------------------------------");
        StringBuffer paramstr = new StringBuffer();
        for (String pkey : param.keySet()) {
            String pvalue = param.get(pkey);
            if (null != pvalue && "" != pvalue) {
                paramstr.append(pkey + "=" + pvalue + "&");
            }
        }
        String paramSrc = paramstr.substring(0, paramstr.length() - 1);
        logger.info("签名原串：" + paramSrc);

        logger.info("生成签名--------------------------------------------");
        sign = sign(paramSrc, pwd);

        logger.info("rsa加密--------------------------------------------");
        paramstr.append("sign=" + sign);
        logger.info("加密原串:" + paramstr);

        String cipherData = encrypt(paramstr.toString(), publicKeyPath);
        logger.info("加密结果:" + cipherData);

        logger.info("发起请求--------------------------------------------");
        responseData = doPost(url, "cipher_data=" + URLEncoder.encode(cipherData));

        logger.info("处理应答--------------------------------------------");
        //国采处理失败时不返回加密字段
        String cipherResponseData = parseXml(responseData);
        logger.info("rsa解密--------------------------------------------");
        String responseData = decryptResponseData(cipherResponseData, privateKeyPath);
        logger.info("responseData=" + responseData);
        String sign = responseData.substring(responseData.indexOf("sign=") + 5, responseData.length());
        String source = responseData.substring(0, responseData.lastIndexOf("&sign"));
        logger.info("source:" + source);
        logger.info("sign:" + sign);

        //rsa验签
        if (verify(source, pwd, sign)) {
            logger.info("验签结果：通过");
        } else {
            logger.info("验签结果：失败");
            return;
        }
    }

    public static PayResponse request(final String url, TreeMap<String, String> param, String pwd, String privateKeyPath, String publicKeyPath) {

        // init
        PayResponse res = new PayResponse();
        StringBuffer paramstr = new StringBuffer();

        logger.info("拼接签名原串-----------------------------------------");
        String paramSrc = joint(param, paramstr);
        logger.info("签名原串：" + paramSrc);

        logger.info("生成签名--------------------------------------------");
        sign = sign(paramSrc, pwd);

        logger.info("rsa加密--------------------------------------------");
        paramstr.append("sign=" + sign);
        logger.info("加密原串:" + paramstr);

        String cipherData = encrypt(paramstr.toString(), publicKeyPath);
        logger.info("加密结果:" + cipherData);

        logger.info("发起请求--------------------------------------------");
        responseData = doPost(url, "cipher_data=" + URLEncoder.encode(cipherData));

        logger.info("处理应答--------------------------------------------");
        String cipherResponseData = parseXml(responseData);
        if (StringUtils.isNotBlank(cipherResponseData)) {
            logger.info("RSA解密--------------------------------------------");
            String responseData = decryptResponseData(cipherResponseData, privateKeyPath);
            logger.info("responseData=" + responseData);

            if (StringUtils.isNotBlank(responseData)) {

                // pass
                TFB8Response tfb8res = gson.fromJson(ParamUtils.toJson(responseData), TFB8Response.class);
                if (tfb8res.getSerialnoState() == TFB8ResponseStateEnum.SUCCESS.getState()) {
                    res.setState(ResponseStateEnum.SUCCESS.getState());
                } else if (tfb8res.getSerialnoState() == TFB8ResponseStateEnum.FAIL.getState()) {
                    res.setState(ResponseStateEnum.FAIL.getState());
                } else {
                    res.setState(ResponseStateEnum.BUSY.getState());
                }
                res.setInfo(tfb8res.getSerialnoDesc());
            } else {
                String info = String.format("RSA解密失败：[%s]", cipherResponseData);
                logger.error(info);

                res.setState(ResponseStateEnum.BUSY.getState());
                res.setInfo(info);
            }
        } else {
            String info = getReturnMsg(responseData);
            logger.error(info);

            res.setState(ResponseStateEnum.BUSY.getState());
            res.setInfo(info);
        }

        return res;
    }

    private static String joint(TreeMap<String, String> param, StringBuffer paramstr) {

        // init
        for (String pkey : param.keySet()) {
            String pvalue = param.get(pkey);
            if (null != pvalue && "" != pvalue) {
                paramstr.append(pkey + "=" + pvalue + "&");
            }
        }
        String paramSrc = paramstr.substring(0, paramstr.length() - 1);

        return paramSrc;
    }

    /**
     * 验签
     *
     * @param source 签名内容
     * @param sign   签名值
     * @return
     */
    private static boolean verify(String source, String pwd, String sign) {
        //MD5验签，把返回的报文串（去掉sign和空串），作MD5加签，然后跟sign 比对
        return (sign(source, pwd).equals(sign)) ? true : false;

    }

    /**
     * 对原串进行签名
     *
     * @param paramSrc the source to be signed
     * @return
     */
    private static String sign(String paramSrc, String pwd) {
        StringBuffer strbuff = new StringBuffer();
        strbuff.append(paramSrc + "&key=" + pwd);
        String sign = null;
        try {
            sign = RSAUtils.getMD5Str(strbuff.toString());
            logger.info("签名:" + sign);
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        return sign;
    }

    /**
     * 加密得到cipherData
     *
     * @param paramstr
     * @param keyPath  GC_PUBLIC_KEY_PATH
     * @return
     */
    private static String encrypt(String paramstr, String keyPath) {

        String publickey = RSAUtils.loadPublicKey(keyPath);

        String cipherData = null;
        try {
            cipherData = RSAUtils.encryptByPublicKey(paramstr.getBytes("UTF-8"), publickey);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cipherData;
    }

    /**
     * rsa解密
     *
     * @param cipherData the data to be decrypt
     * @param keyPath    PRIVATE_KEY_PATH
     * @return
     */
    private static String decryptResponseData(String cipherData, String keyPath) {

        String privatekey = RSAUtils.loadPrivateKey(keyPath);
        String result;

        try {
            result = RSAUtils.decryptByPrivateKey(Base64.decode(cipherData), privatekey);
            logger.info("解密结果:" + result);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String parseXml(String responseData) {
        String cipher_data = null;

        try {
            Document dom = DocumentHelper.parseText(responseData);
            Element root = dom.getRootElement();
            cipher_data = root.element("cipher_data").getText();
        } catch (DocumentException e1) {
            e1.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cipher_data;
    }

    private static String getReturnMsg(String responseData) {
        String msg = null;

        try {
            Document dom = DocumentHelper.parseText(responseData);
            msg = dom.getRootElement().element("retmsg").getText();
        } catch (DocumentException e1) {
            e1.printStackTrace();
        }
        return msg;
    }

    public static String doPost(String url, String param) {
        PrintWriter out = null;
        BufferedReader in = null;
        String result = "";
        try {
            URL realUrl = new URL(url);
            // 打开和URL之间的连接
            URLConnection conn = realUrl.openConnection();
            // 设置通用的请求属性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 发送POST请求必须设置如下两行
            conn.setDoOutput(true);
            conn.setDoInput(true);
            // 获取URLConnection对象对应的输出流
            out = new PrintWriter(conn.getOutputStream());
            // 发送请求参数
            out.print(param);
            // flush输出流的缓冲
            out.flush();
            // 定义BufferedReader输入流来读取URL的响应
            in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            result = IOUtils.toString(in);
            logger.info("请求结果:" + result);
        } catch (Exception e) {
            logger.info("发送 POST 请求出现异常！" + e);
            e.printStackTrace();
        }
        // 使用finally块来关闭输出流、输入流
        finally {
            IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(in);
        }
        return result;
    }
}
